package com.peanut.bosskg.config;

/**
 * 生成唯一ID，每秒可生成4096*1000个
 */
public class IdWorkerBySnowFlake {
    //数据中心ID
    private long dataCenterId;
    //数据中心某台机器ID
    private long workerId;
    //代表一毫秒内生成的多个id的最新序号
    private long sequence;
    //开始生成ID的时间
    private final long twepoch = 1585644268888L;
    //设置数据中心id位数
    private long dataCenterIdBits = 5L;
    //设置数据中心机器id位数
    private long workerIdBits = 5L;
    //每毫秒内产生的id数 2 的 12次方
    private long sequenceBits = 12L;
    //数据中心id最大值
    private long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits);
    //机器id最大值
    private long maxWorkerId = -1L ^ (-1L << workerIdBits);
    /**
     * 设置左移位数
     **/
    private long workerIdShift = sequenceBits;
    private long dataCenterIdShift = sequenceBits + workerIdBits;
    private long timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits;

    private long sequenceMask = -1L ^ (-1L << sequenceBits);
    //记录上一次生成id的时间产生时间毫秒数，判断是否是同一毫秒
    private long lastTimestamp = -1L;


    public IdWorkerBySnowFlake(long dataCenterId, long workerId) {
        // 检查机房id和机器id是否超过31 不能小于0
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(
                    String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }

        if (dataCenterId > maxDataCenterId || dataCenterId < 0) {
            throw new IllegalArgumentException(
                    String.format("dataCenter Id can't be greater than %d or less than 0", maxDataCenterId));
        }
        this.workerId = workerId;
        this.dataCenterId = dataCenterId;
    }

    /**
     * @return
     */
    public synchronized long nextId() {
        // 获取当前时间
        long timestamp = timeGen();
        //当前服务器时间如果小于上一次时间，说明服务器时间异常，此时生成ID可能会导致重复，拒绝生成id
        if (timestamp < lastTimestamp) {
            System.err.printf(
                    "服务器时间发生了回拨。在%d之前拒绝请求.", lastTimestamp);
            throw new RuntimeException(
                    String.format("“服务器时间发生了回拨。%d毫秒内拒绝生成id",
                            lastTimestamp - timestamp));
        }

        // 同一个毫秒内，发送请求生成d，序列递增
        if (lastTimestamp == timestamp) {
            //这个位运算保证始终就是在4096这个范围内，避免你自己传递个sequence超过了4096这个范围
            sequence = (sequence + 1) & sequenceMask;
            //当某一毫秒的时间，产生的id数 超过4096
            if (sequence == 0) {
//              循环等待直到获得下一毫秒时间
                timestamp = tilNextMillis(lastTimestamp);
            }

        } else {
            sequence = 0;
        }
        lastTimestamp = timestamp;
        // 这儿就是最核心的二进制位运算操作，移位并拼接，生成一个64bit的id
        return ((timestamp - twepoch) << timestampLeftShift) |
                (dataCenterId << dataCenterIdShift) |
                (workerId << workerIdShift) | sequence;
    }

    /**
     * 循环等待直到获得下一毫秒时间
     *
     * @param lastTimestamp
     * @return timestamp  下一毫秒
     */
    private long tilNextMillis(long lastTimestamp) {

        long timestamp = timeGen();

        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 获取当前时间
     */
    private long timeGen() {
        return System.currentTimeMillis();
    }

}
